package com.grm.distributed.lock.service;

import com.grm.distributed.lock.common.Result;
import com.grm.distributed.lock.mapper.ProductMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * redis基于setnx指令实现分布式锁-重试（循环获取）
 *
 * 判断和释放锁之间，保证原子性（lua脚本）
 *      lua脚本：一次性发送多个指令给redis，redis单线程执行指令遵守one by one规则
 *      eval script numkeys key [key ...] arg [arg ...]
 *       eval "return redis.call('set',KEYS[1],ARGV[1])" 1 lock uuid
 *
 *       eval "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 lock uuid
 */
@Slf4j
@Service
public class ProductService9 {
    @Autowired
    private ProductMapper productMapper;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public Result sell() {
        // 1. UUID防误删
        String uuid = UUID.randomUUID().toString();
        // 1. 执行setnx加锁,setIfPresent：存在就设置，setIfAbsent：不存在就设置(setnx),设置过期时间
        // 2. 获取锁失败，重试:循环重试
        while (!stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,3,TimeUnit.SECONDS)) {
            try {
                Thread.sleep(30);
                log.info("循环重试获取锁中：{}",uuid);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        try {
            // 3. 查库存
            String productCountStr = stringRedisTemplate.opsForValue().get("product_count");
            Integer count = Integer.valueOf(productCountStr);
            // 4. 扣减库存
            if (count > 0) {
                count = count - 1;
            }
            log.info("product current count = {}", count);
            stringRedisTemplate.opsForValue().set("product_count", String.valueOf(count));
        } finally {
            // 5. 解锁，lua脚本释放锁
            String script = "if redis.call('get',KEYS[1]) == ARGV[1] " +
                "then " +
                "   return redis.call('del',KEYS[1]) " +
                "else " +
                "   return 0 " +
                "end";
                stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList("lock"),uuid);
        }
        return Result.success();
    }
}
